#include "textrender.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <utility>
#include <algorithm>

///////////////////////////////////////////////////////////////////////////////

static const char s_VertexSource[] = {
#include "shader.h"
#include "textrender.vtx.h"
};

static const char s_FragmentSource[] = {
#include "shader.h"
#include "textrender.frg.h"
};

static const char s_Font[] = {
#include "font.h"
};

///////////////////////////////////////////////////////////////////////////////

TextRender::TextRender()
{
  GLuint vertexShader = compileShader(GL_VERTEX_SHADER, s_VertexSource, sizeof(s_VertexSource));
  GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, s_FragmentSource, sizeof(s_FragmentSource));
  m_program = glCreateProgram();
  glAttachShader(m_program, vertexShader);
  glAttachShader(m_program, fragmentShader);
  glLinkProgram(m_program);
  assert(glGetError() == 0);
  glDeleteShader(vertexShader);
  glDeleteShader(fragmentShader);

  m_posAttrib = glGetAttribLocation(m_program, "a_pos");
  m_stAttrib = glGetAttribLocation(m_program, "a_st");
  m_textureSampler = glGetUniformLocation(m_program, "s_texture");
  assert(glGetError() == 0);

  assert(sizeof(s_Font) == 8*(128-32));
  uint8_t texture[8][8*(128-32)][4];
  for(uint c=0; c<128-32; c++)
  {
    for(uint y=0; y<8; y++)
    {
      for(uint x=0; x<8; x++)
      {
        bool pix = (s_Font[c*8+y]>>(7-x))&1;
        texture[y][c*8+x][0] = 255;
        texture[y][c*8+x][1] = 255;
        texture[y][c*8+x][2] = 255;
        texture[y][c*8+x][3] = pix ? 255 : 0;
      }
    }
  }

  glGenTextures(1, &m_texture);
  assert(m_texture > 0);
  glBindTexture(GL_TEXTURE_2D, m_texture);
  assert(glGetError() == 0);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8*(128-32), 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
  assert(glGetError() == 0);
  //glGenerateMipmap(GL_TEXTURE_2D);
  assert(glGetError() == 0);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  assert(glGetError() == 0);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  assert(glGetError() == 0);
  glBindTexture(GL_TEXTURE_2D, 0);
  assert(glGetError() == 0);

  glGenBuffers(1, &m_vertexBuffer);

  m_viewportWidth = 0;
  m_viewportHeight = 0;
}

TextRender::~TextRender()
{
  glDeleteProgram(m_program);
  glDeleteTextures(1, &m_texture);
}

void TextRender::resize(int width, int height)
{
  m_viewportWidth = width;
  m_viewportHeight = height;
}


struct Vertex { vec2 pos; vec2 st; };

static const float s_scale = 2.0f;

void TextRender::draw(const vec2 &pos, const char *textfmt, ...)
{
  char text[256];
  va_list varg;
  va_start(varg, textfmt);
  size_t textlen = vsnprintf(text, sizeof(text), textfmt, varg);
  va_end(varg);

  //int viewport[4];
  //glGetIntegerv(GL_VIEWPORT, viewport);
  //ASSERT_GLERROR();

  //Vertex vertices[textlen*6];
  Vertex *vertices = (Vertex*)alloca(textlen*6*sizeof(Vertex));

  float texWidth = 128-32;

  vec2 cursor;
  cursor.x = refitf(4+pos.x*8*s_scale, 0, m_viewportWidth, -1.0f, 1.0f);
  cursor.y = refitf(4+pos.y*8*s_scale, 0, m_viewportHeight, 1.0f, -1.0f);
  vec2 cursoradvancex(refitf(8*s_scale, 0, m_viewportWidth, 0.0f, 2.0f), 0.0f);
  vec2 cursoradvancey(0.0f, refitf(8*s_scale, 0, m_viewportHeight, 0.0f, 2.0f));

  for(uint i=0; i<textlen; i++)
  {
      unsigned char c = text[i];
      if(c<32 || c>=128) c ='?';
      c -= 32;
      vertices[i*6+0].pos = cursor;
      vertices[i*6+0].st = vec2(c/texWidth, 0.0f);
      vertices[i*6+1].pos = cursor + cursoradvancex;
      vertices[i*6+1].st = vec2((c+1)/texWidth, 0.0f);
      vertices[i*6+2].pos = cursor + cursoradvancex - cursoradvancey;
      vertices[i*6+2].st = vec2((c+1)/texWidth, 1.0f);
      vertices[i*6+3].pos = cursor - cursoradvancey;
      vertices[i*6+3].st = vec2(c/texWidth, 1.0f);
      vertices[i*6+4] = vertices[i*6+0];
      vertices[i*6+5] = vertices[i*6+2];
      cursor += cursoradvancex;
  }

  glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
  glBufferData(GL_ARRAY_BUFFER, textlen*6*sizeof(Vertex), vertices, GL_DYNAMIC_DRAW);
  ASSERT_GLERROR();

  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glBlendEquation(GL_FUNC_ADD);
  glEnable(GL_BLEND);
  ASSERT_GLERROR();

  glUseProgram(m_program);
  ASSERT_GLERROR();

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, m_texture);
  glUniform1i(m_textureSampler, 0);
  ASSERT_GLERROR();

  glVertexAttribPointer(m_posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
  glEnableVertexAttribArray(m_posAttrib);
  ASSERT_GLERROR();

  glVertexAttribPointer(m_stAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)8);
  glEnableVertexAttribArray(m_stAttrib);
  ASSERT_GLERROR();

  glDrawArrays(GL_TRIANGLES, 0, textlen*6);
  ASSERT_GLERROR();

  glDisableVertexAttribArray(m_posAttrib);
  glDisableVertexAttribArray(m_stAttrib);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  ASSERT_GLERROR();

  glDisable(GL_BLEND);
  ASSERT_GLERROR();
}
